﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace ProDivingAdmin2salmon14
{
    public class CommunicationServer
    {
        #region Data members
        private static byte[] _buffer = new byte[1024];
        private static List<Socket> _newClientSockets = new List<Socket>();
        private static Socket _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        private static Socket _broadcastSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        public static string sendText { get; set; }
        public static Dictionary<Judge, Socket> _judgeSockets { get; set; }
        #endregion

        #region Public static methods
        
        public static void BroadCastIP() 
        {
            byte[] byteIP = Encoding.ASCII.GetBytes(GetLocalIPAddress());

            IPEndPoint endPoint1 = new IPEndPoint(IPAddress.Broadcast, 9050);
            IPEndPoint endPoint2 = new IPEndPoint(IPAddress.Parse("192.168.100.255"), 9050);

            _broadcastSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            _broadcastSocket.SendTo(byteIP, endPoint1);
            _broadcastSocket.SendTo(byteIP, endPoint2);
            System.Console.WriteLine("Server broadcasted!");
        }
        /// <summary>
        ///  Setting up server, Binds the server to the local IP.
        ///  Says that the server socket takes in new connections to it, by calling an event everytime it gets a new client connected to it.
        /// </summary>
        public static void SetupServer()
        {
            _judgeSockets = new Dictionary<Judge, Socket>();
            Console.WriteLine("Setting up Server....");
            _serverSocket.Bind(new IPEndPoint(IPAddress.Any, 250));
            _serverSocket.Listen(5);
            _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        }

        /// <summary>
        /// Function sends data to all the sockets in the clientsocket list.
        /// </summary>
        public static void SendDive()
        {
            byte[] data = Encoding.ASCII.GetBytes(sendText);
            foreach (var socket in _judgeSockets.Values)
            {
                if (socket != null)
                {
                    try
                    {
                        socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
                    }
                    catch (SocketException e)
                    {
                        System.Console.WriteLine(e.Message);
                        RemoveDC();
                    }
                    System.Console.WriteLine(sendText);
                }
            }
        }

        public static void SendDive(string diveCode)
        {
            byte[] data = Encoding.ASCII.GetBytes(diveCode);
            foreach (var socket in _judgeSockets.Values)
            {
                if (socket != null)
                {
                    try
                    {
                        socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
                    }
                    catch (SocketException e)
                    {
                        System.Console.WriteLine(e.Message);
                        RemoveDC();
                    }
                }
            }
        }
        #endregion

        #region Private static methods
        public static string GetLocalIPAddress()
        {
            IPHostEntry host;
            string localIP = "";
            host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    localIP = ip.ToString();
                    break;
                }
            }
            return localIP;
        }
        /// <summary>
        /// Function gets called everytime someone connects to the serversocket.
        /// Sets up an event everytime the clientsocket sends something to the serversocket.
        /// And adds the connecting socket to it's clientsocket list.
        /// </summary>
        /// <param name="AR"></param>
        private static void AcceptCallback(IAsyncResult AR)
        {
            Socket socket = _serverSocket.EndAccept(AR);
            _newClientSockets.Add(socket);
            Console.WriteLine("Client connected");
            socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socket);
            _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        }

        /// <summary>
        /// Function gets called everytime a connected client sends data to the serversocket.
        /// It receives data and also sends back a reply to the client.
        /// </summary>
        /// <param name="AR"></param>
        private static void ReceiveCallback(IAsyncResult AR)
        {
            try
            {
                Socket socket = (Socket)AR.AsyncState;
                int received = socket.EndReceive(AR);

                byte[] dataBuff = new byte[received];
                Array.Copy(_buffer, dataBuff, received);

                string text = Encoding.ASCII.GetString(dataBuff);


                string response = "";
                if (text.ToLower().Contains(","))
                {
                    Contest.judgeScores.Add(text);
                    Contest.judgeScores.Clear();
                }
                else if (text.ToLower().Contains(':'))
                {
                    Judge responseJudge = new Judge(Contest.GetJudgeByID(Int32.Parse(text.Trim(':'))));
                    response = "Name:" + responseJudge.fullName;

                    if (!CheckJudgeInJudgeSocket(socket, responseJudge) && !response.Contains("NONE"))
                    {
                        SetJudgeToSocket(socket, responseJudge);
                    }
                }
                System.Console.WriteLine("Server text received: " + text);

                byte[] data = Encoding.ASCII.GetBytes(response);
                socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
                socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socket);
            }
            catch (SocketException e)
            {                
                System.Console.WriteLine(e.Message);
                RemoveDC();
            }

        }

        /// <summary>
        /// Function refreshes the list of connected sockets, removes the sockets that have dissconnected.
        /// </summary>
        private static void RemoveDC()
        {
            foreach (var socket in _judgeSockets)
            {
                if (socket.Value != null)
                {
                    if (!IsSocketConnected(socket.Value))
                    {
                        _judgeSockets.FirstOrDefault(x => x.Value == socket.Value).Key.isConnected = false;
                        Contest.SetJudgeDC(_judgeSockets.FirstOrDefault(x => x.Value == socket.Value).Key);
                        _judgeSockets[socket.Key] = null;
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Checks if a socket is connected or not.
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        private static bool IsSocketConnected(Socket s)
        {
            bool part1 = s.Poll(1000, SelectMode.SelectRead);
            bool part2 = (s.Available == 0);

            if ((part1 && part2) || !s.Connected)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// Function event gets called when the serversocket sends data to a connected client socket.
        /// </summary>
        /// <param name="AR"></param>
        private static void SendCallback(IAsyncResult AR)
        {
            Socket socket = (Socket)AR.AsyncState;
            socket.EndSend(AR);
        }
        private static bool CheckJudgeInJudgeSocket(Socket socket, Judge judge) 
        {
            foreach (var judgeKey in _judgeSockets.Keys) 
            {
                if (judgeKey.ID == judge.ID) 
                {
                    _newClientSockets.Remove(socket);
                    judgeKey.isConnected = true;
                    _judgeSockets[judgeKey] = socket;
                    if (sendText != "" && sendText != null)
                    {
                        SendDive();
                    }
                    return true;
                }
            }
            return false;
        }

        private static void SetJudgeToSocket(Socket socket , Judge judge)
        {
            _judgeSockets.Add(judge, socket);
            _newClientSockets.Remove(socket);
        }
        #endregion
    }
}
